#include "Strategy.hh"

/// Enemy Counter Functor
struct balance_counter_t
{
  unsigned myteam;
  unsigned enemies;
  unsigned friends;
  unsigned invisible;
  unsigned total;


  balance_counter_t(unsigned _myteam) :
    myteam(_myteam), enemies(0), friends(0),
    invisible(0), total(0)
  {}

  bool operator()(State & state, Location & sLoc, Location & cLoc, unsigned depth)
  {
    if(state.location(cLoc).ant != -1 and state.location(cLoc).ant != myteam)
      ++enemies;
    else if(state.location(cLoc).ant != -1 and state.location(cLoc).ant == myteam)
      ++friends;
    else if(!state.location(cLoc).isVisible)
      ++invisible;
    ++total;
    return true;
  }
};


/// Look around and count from sLoc
void look_around(State & state, Location sLoc, unsigned team, double radius,
		 unsigned & enemies, unsigned & friends, unsigned & invisible)
{
  balance_counter_t balance_counter(team);

  fly(state, sLoc, radius,  balance_counter);
  
  enemies = balance_counter.enemies;
  friends = balance_counter.friends;
  invisible = balance_counter.invisible;
}




/// Count reachable enemies according to the rules if one ant of team is at loc
unsigned count_reachable_enemies(State & state, Location sLoc, unsigned team)
{
  balance_counter_t enemy_count(team);

  fly(state, sLoc, state.attackradius,  enemy_count);

  return enemy_count.enemies;
}


/// Count reachable enemies according to the rules for a given ant position
/// assume my team if empty position
unsigned count_reachable_enemies(State & state, Location sLoc)
{
  assert(!state.myAnts.empty());
  unsigned team = state.location(sLoc).ant;
  if(state.location(sLoc).ant == -1) team = state.location(state.myAnts.front()).ant;
  return count_reachable_enemies(state, sLoc, team);
}



/// Estimates power balance for a given position
double power_balance(State & state, Location sLoc, unsigned team, unsigned distance, double alpha = 2.0)
{
  balance_counter_t balance_count(team);

  walk(state, sLoc, distance, balance_count);

  return balance_count.friends - alpha * (balance_count.enemies + 0.1 * balance_count.invisible);
}


/// Help me attraction/repulsion potential
/// positive when position need help
/// negative if safe
double help_me(State & state, Location sLoc, unsigned team, unsigned distance, double alpha)
{
  return 2.0/(1.0+exp(power_balance(state, sLoc, team, distance, alpha))) - 1.0;
}



/// estimate safety level of positions according to neighbourhood
/// rought estimation: only for path-finding
void estimate_rough_safety(State & state)
{
  assert(!state.myAnts.empty());
  unsigned myteam = state.location(state.myAnts.front()).ant;
  balance_counter_t counter(myteam);

  // compute zero step safety
  for(unsigned row=0; row!=state.rows; ++row)
    for(unsigned col=0; col!=state.cols; ++col)
      {
	if(!state.grid[row][col].isWater)
	  {
	    Location sLoc(row,col);
	    fly(state, sLoc, state.attackradius,counter);
	    if(counter.friends >= counter.enemies)
	      state.grid[row][col].isSafe = true;
	  }
      }
}




/// Light Battle engine:
/// Functor trying to predict battle issues
struct LightBattleEngine_t
{
  unsigned myteam;
  unsigned my_enemies;
  bool I_die;
  unsigned killed_enemies;

  LightBattleEngine_t(State &state, unsigned _myteam) :
    myteam(_myteam)
  {
    my_enemies = 0;
    I_die = false;
    killed_enemies = 0;
  }

  void init(State & state, Location sLoc)
  {
    my_enemies = count_reachable_enemies(state, sLoc);
    I_die = false;
    killed_enemies = 0;
  }


  bool operator()(State & state, Location & sLoc, Location & cLoc, unsigned depth)
  {
    if(state.location(cLoc).ant != -1 and state.location(cLoc).ant != myteam)
      {
	unsigned his_enemies = count_reachable_enemies(state, cLoc);
        I_die = (his_enemies <= my_enemies);
	if(his_enemies == my_enemies) killed_enemies++;
      }
    return true;
  }

};


/// Light and local Battle management
/// evaluates if a position is expected to die at t+1
bool will_die(State & state, Location sLoc, unsigned & enemies_killed)
{
  assert(!state.myAnts.empty());
  unsigned myteam = state.location(state.myAnts.front()).ant;

  LightBattleEngine_t attack(state, myteam);
  attack.init(state,sLoc);
  fly(state, sLoc, state.attackradius,attack);

  enemies_killed = attack.killed_enemies;
  return attack.I_die;
}






/// Linear potential
struct update_value_lin_t
{
  double w;
  unsigned mindepth;

  update_value_lin_t(double _w, unsigned _min = 1) : w(_w), mindepth(_min) {}

  bool operator()(State & state, Location & sLoc, Location & cLoc, unsigned depth)
  {
    if(depth >= mindepth)
      state.location(cLoc).value += w / (1.0 + depth - mindepth);
    return true;
  }
};



void
set_potentials(State & state)
{
  assert(!state.myAnts.empty());
  unsigned myteam = state.location(state.myAnts.front()).ant;

  state.bug << "estimate safety level" << std::endl;
  // estimate rough safety level for path-finding
  estimate_rough_safety(state);

  // set initial value to force exploration
  for(unsigned row=0;row!=state.rows; ++row)
    for(unsigned col=0;col!=state.cols; ++col)
      state.grid[row][col].value = explorationw/(1.0 + state.grid[row][col].visits);

  state.bug << "food" << std::endl;
  // food
  if(!state.myHills.empty())
    {
      update_value_lin_t update_food(foodw,1);
      for(unsigned i=0; i!=state.food.size(); ++i)
        {
          safe_walk(state,state.food[i],state.viewradius*20,update_food);
        }
    }

  state.bug << "my hills" << std::endl;
  // my hills
  for(unsigned i=0; i!=state.myHills.size(); ++i)
    {
      double helpw = help_me(state, state.myHills[i], myteam, state.viewradius*2);

      update_value_lin_t update_my_hill(myhillw * helpw ,2);

      if(state.myAnts.size()<10 or state.turn % 20 >=10 or helpw>0)
        state.location(state.myHills[i]).value = -1e6;

      walk(state,state.myHills[i],state.viewradius*100,update_my_hill);
    }



  state.bug << "my friends" <<  std::endl;
  // friends
  for(unsigned i=0; i!=state.myAnts.size(); ++i)
    {

      bool blokus = true;
      for(int d=0; d<TDIRECTIONS; d++)
        {
          Location nLoc = state.getLocation(state.myAnts[i], d);
          if(state.location(nLoc).ant == -1)
            blokus = false;
        }

      if(!blokus)
        {
          double helpw = help_me(state, state.myAnts[i], myteam, state.viewradius);
          update_value_lin_t update_friend(friendw*helpw,1);
          walk(state, state.myAnts[i],state.attackradius,update_friend);
          state.location(state.myAnts[i]).visits++;
        }
    }


  state.bug << "Enemy hills" <<  std::endl;
  // enemy hills
  update_value_lin_t update_hills(hillw,0);
  for(unsigned i=0; i!=state.enemyHills.size(); ++i)
    safe_walk(state,state.enemyHills[i],state.viewradius*100,update_hills);

  
  // boost safe locations
  for(unsigned row=0;row!=state.rows; ++row)
    for(unsigned col=0;col!=state.cols; ++col)
      if(state.grid[row][col].isSafe) state.grid[row][col].value += 1.0;

}

void update_map_knowledge(State & state, cache_state_info_t & memory)
{
    std::set<Location>::iterator it=memory.enemyHills.begin();
    //erase out of date information
    while(it!=memory.enemyHills.end())
    {
        std::set<Location>::iterator next=it;
        next++;
        if(state.location(*it).isVisible)
            if(! state.location(*it).isHill || (state.location(*it).hillPlayer == 0))
                memory.enemyHills.erase(it);
        it=next;
    }
    //update places we can see now
    memory.enemyHills.insert(state.enemyHills.begin(),state.enemyHills.end());
    //mix places from sight and memory
    state.enemyHills.clear();
    state.enemyHills.insert(state.enemyHills.begin(),memory.enemyHills.begin(),memory.enemyHills.end());

    it=memory.food.begin();
    //erase out of date information
    while(it!=memory.food.end())
    {
        std::set<Location>::iterator next=it;
        next++;
        if(state.location(*it).isVisible)
            if(! state.location(*it).isFood)
                memory.food.erase(it);
        it=next;
    }
    //update places we can see now
    memory.food.insert(state.food.begin(),state.food.end());
    //mix places from sight and memory
    state.food.clear();
    state.food.insert(state.food.begin(),memory.food.begin(),memory.food.end());
}
